using System;
using System.Diagnostics;
using System.Collections.Generic;

namespace OpenRay
{
	//Immutable class
	public class SphereGeometry : Geometry
	{
		private readonly float radiusSquared;
		
		public SphereGeometry ( IList<Point> v, IList<Material> m )
			: base ( v, new List<Vector>(), m )
		{	
			Debug.Assert( v.Count == 2, "Incorrect number of points given" );
			Debug.Assert( m.Count == 1, "Incorrect number of materials given" );
			
			Vector temp = v[0] - v[1];
			radiusSquared = Vector.DotProduct( temp, temp );
		}
		
		public SphereGeometry ( IList<Point> v, float radius, IList<Material> m )
			: base ( v, new List<Vector>(), m )
		{
			Debug.Assert( v.Count == 1, "Incorrect number of points given" );
			Debug.Assert( m.Count == 1, "Incorrect number of points given" );
			Debug.Assert( radius > 0, "Radius supplied is too small" );
			
			radiusSquared = radius * radius;
		}
		
		public override IList<GeometryHit> Trace(Ray r)
		{
			List<GeometryHit> ret = new List<GeometryHit>();
			
			Vector oc = vertices[0] - r.Origin;
			float l2oc = Vector.DotProduct( oc, oc );
			
			if ( l2oc <= radiusSquared + Delta ) //Inside Sphere
			{
				float tca = Vector.DotProduct( oc, r.Direction );
				float l2hc = ( radiusSquared - l2oc ) / Vector.DotProduct( r.Direction, r.Direction ) + tca * tca;
				float temp = (float)Math.Sqrt( l2hc );
				
				float t = tca + temp;
				//float d = tca - temp;
				
				if ( t > Delta )
				{
					ret.Add( new GeometryHit(this, r, t) );	
				}
			}
			else //Outside Sphere
			{
				float tca = Vector.DotProduct( oc, r.Direction ) / Vector.DotProduct( r.Direction, r.Direction );
				
				if ( tca > 0 ) //Points at sphere
				{
					float l2hc = ( radiusSquared - l2oc ) / Vector.DotProduct( r.Direction, r.Direction ) + tca * tca;
            		float temp = (float)Math.Sqrt( l2hc );
            		float t = tca - temp;
					
		            if( l2hc > 0 && t > Delta ) //Hits Sphere
					{
						ret.Add( new GeometryHit( this, r, t ) );
						//ret.Add( new GeometryHit( this, r, tca + temp ) );
		            }
				}
			}
			
			
			return ret;
		}
		
		public override Material Material (Point modelHit, InterpolationData interpolation)
		{
			return materials[0];
		}
		
		public override Vector Normal (Point modelHit, InterpolationData interpolation)
		{
			return Vector.Normalize( modelHit - vertices[0] );
		}
		
		public override BoundingBox BoundingBox ()
		{
			float radius = (float)Math.Sqrt( radiusSquared );
			Vector temp = new Vector( radius, radius, radius );
			
			return new BoundingBox( vertices[0] - temp, vertices[0] + temp );
		}
	}
}

